//
//  traitementTokens.h
//  projet
//
//  Created by François Reme on 09/11/12.
//  Copyright 2012 KingTao. All rights reserved.
//
#ifndef _traitementotken_hpp
#define _traitementotken_hpp
#include <iostream>
#include <cstddef>
#include <cstdio>
#include <string.h>  

#include "type/Type.hpp"
#include "SymbTable.hpp"
#include "IdentTable.hpp"
#include "ConteneurCode.hpp"
#include "Instruction.hpp"
#include "Operande.hpp"
#include "IdentTable.hpp"
#include "../inc/Code3ad.hpp"
#include "categorie/Variable.hpp"
#include <string>



using std::stringstream;
using std::cout;
using std::map;
using std::endl;
using std::string;
using std::pair;

extern int yyerror ( char* );
extern IdentTable t;
extern ConteneurCode * CCode;
extern SymbTable * tableCourante;
int etiquette = 0;
void createIf(c3a & cond,c3a & code){
  if(cond.calcule){
    
  }else{
    stringstream sstm;
    sstm << "_et" << etiquette ;
    etiquette++;
    string s = sstm.str();	
    size_t size = s.size() + 1;
    char * buffer = new char[ size ];
    strncpy( buffer, s.c_str(), size );
    char * etiq = buffer;
    CCode->ajouterInstruction(
        new Instruction(OPCODE_JMPC,
        Operande(t.getName(cond.id)),
                        Operande(etiq)
        ));
    CCode->ajouterInstruction(
                              new Instruction(OPCODE_JMPC,
                                              Operande(t.getName(cond.id)),
                                              Operande(etiq)
                                              ));
  }

}
void CallFunc(c3a & $1){
  if(t.getName($1.id) != 0){
    
    CCode->ajouterInstruction(
                              new Instruction(OPCODE_CLL,
                                              Operande( t.getName($1.id))
                                              )
                              );
  }else{
    yyerror((char * )"Probleme table des symboles...");
  }
}
void detectArgs(c3a & $$, c3a & $1,c3a & $2){
  CCode->ajouterInstruction(new Instruction(OPCODE_POP, Operande(t.getName($1.id))));
}
void marquerFin(){
  CCode->ajouterInstruction(new Instruction(OPCODE_NONE, (char * )"**"));
}
void ProcDeclFunc(c3a & $$, c3a & $1,c3a & $3){
  
  //printf("FUNCTION name = %s\n", t.getName($1.id));
  //cout << tableCourante->getContexteParent() << endl;
}
void ProgHeaderFunc( c3a & $2){
  printf("PROGRAM name = %s\n", t.getName($2.id));
  Instruction * instr = new Instruction( OPCODE_NONE , t.getName($2.id) );
  //ajout de l'instruction au code 3@ global
  CCode->ajouterInstruction(instr);
}
void ProcHeaderFunc(c3a & $$, c3a & $2){
  printf("FUNCTION name = %s\n", t.getName($2.id));
  Instruction * instr = new Instruction( OPCODE_NONE , t.getName($2.id) );
  //ajout de l'instruction au code 3@ global
  CCode->ajouterInstruction(instr);
}
void ExprOP_ADDExpr(c3a & $$, c3a & $1, c3a & $3){
    // en cas de types différents mais compatible, (int) + (float) ou (float)+(int) on cast les deux en float et on fait l'ajout.
    if($1.calcule && $3.calcule && ( ($1.type == Type::TYPE_INTEGER && $3.type == Type::TYPE_REAL) || ($3.type == Type::TYPE_INTEGER && $1.type == Type::TYPE_REAL) ) )
    {
        switch($1.type){
            case Type::TYPE_INTEGER :
                $1.type = Type::TYPE_REAL;
                $1.f = $1.i;
                break;
            case Type::TYPE_REAL :
                $3.type = Type::TYPE_REAL;
                $3.f = $3.i;
                break;							
        }
    }
    if($1.type != $3.type){
        yyerror((char*)"Addition de deux types incompatibles");
    }
    if($1.calcule && $3.calcule){
        if( $1.type == $3.type){
            switch($1.type){
                case Type::TYPE_INTEGER :
                    $$.i = $1.i + $3.i;
                    break;
                case Type::TYPE_REAL :
                    $$.f = $1.f + $3.f;
                    break;
                case Type::TYPE_STRING :
                    string stemp = $1.chet;
                    stemp += $3.chet;
                    size_t size = stemp.size() + 1;
                    char * buffer = new char[ size ];
                    strncpy( buffer, stemp.c_str(), size );
                    $$.chet = buffer;
                    break;
              				
            }
            $$.type = $1.type;
            $$.calcule = true;
        }
    }else if($1.calcule && !$3.calcule){
      // creation d'un temporaire
      int idnTemp = t.ajouterTemporaire();
      // recupération du de la derniere ligne dans le code 3@ avec pour operande gauche la variable qu'on cherche a ajouter
      Instruction lastLigne$3 = CCode->getLastVarAff(t.getName($3.id));
        //ajout du temporaire a la table des symboles 
        // TODO : on aura un probleme lors de l'appel de fonction
        tableCourante->ajouterTemporaire(idnTemp, $3.type);
        // Ligne clef on met dans le temporaire la somme des deux operande de l'addition.
        Instruction * instr = new Instruction( OPCODE_ADD,
                                              Operande(t.getName(idnTemp)),
                                              Operande($1),
                                              Operande(t.getName($3.id))
                                              );
        //ajout de l'instruction au code 3@ global
        CCode->ajouterInstruction(instr);
      // hyper important on dit que la valeur de retour (.res dans les TD) est le temporaire, c'est la fonction d'affectation qui s'occupera de tout mettre bien
      $$.id = idnTemp;
      $$.type = $1.type;
      $$.calcule = false;
      
    }else if(!$1.calcule && $3.calcule){
      int idnTemp = t.ajouterTemporaire();
      Instruction lastLigne$1 = CCode->getLastVarAff(t.getName($1.id));
      
        tableCourante->ajouterTemporaire(idnTemp, $1.type);
        Instruction * instr = new Instruction( OPCODE_ADD,
                                              Operande(t.getName(idnTemp)),
                                              Operande(t.getName($1.id)),
                                              Operande($3)
                                              );
        CCode->ajouterInstruction(instr);
      
      $$.id = idnTemp;
      $$.type = $3.type;
      $$.calcule = false;
    }else{
      int idnTemp = t.ajouterTemporaire();
      Instruction lastLigne$3 = CCode->getLastVarAff(t.getName($3.id));
      tableCourante->ajouterTemporaire(idnTemp, $3.type);
      Instruction * instr = new Instruction( OPCODE_ADD,
                                              Operande(t.getName(idnTemp)),
                                              Operande(t.getName($1.id)),
                                              Operande(t.getName($3.id))
                                              );
      CCode->ajouterInstruction(instr);
      $$.id = idnTemp;
      $$.type = $1.type;
      $$.calcule = false;
    }
}
// debut ajout du OP moin //

void ExprOP_SUBExpr(c3a & $$, c3a & $1, c3a & $3){
    if($1.calcule && $3.calcule && ( ($1.type == Type::TYPE_INTEGER && $3.type == Type::TYPE_REAL) || ($3.type == Type::TYPE_INTEGER && $1.type == Type::TYPE_REAL) ) )
    {
        switch($1.type){
            case Type::TYPE_INTEGER :
                $1.type = Type::TYPE_REAL;
                $1.f = $1.i;
                break;
            case Type::TYPE_REAL :
                $3.type = Type::TYPE_REAL;
                $3.f = $3.i;
                break;							
        }
    }
  if($1.type != $3.type){
      yyerror((char*)"Soustraction de deux types incompatibles");
  }
    if($1.calcule && $3.calcule){
        if( $1.type == $3.type){
            switch($1.type){
                case Type::TYPE_INTEGER :
                    $$.i = $1.i - $3.i;
                    break;
                case Type::TYPE_REAL :
                    $$.f = $1.f - $3.f;
                    break;							
            }
            $$.type = $1.type;
            $$.calcule = true;
        }
    }else if($1.calcule && !$3.calcule){
    	
      int idnTemp = t.ajouterTemporaire();
      // recupération du de la derniere ligne dans le code 3@ avec pour operande gauche la variable qu'on cherche a ajouter
      Instruction lastLigne$3 = CCode->getLastVarAff(t.getName($3.id));
        //ajout du temporaire a la table des symboles 
        // TODO : on aura un probleme lors de l'appel de fonction
        tableCourante->ajouterTemporaire(idnTemp, $3.type);
        // Ligne clef on met dans le temporaire la somme des deux operande de l'addition.
        Instruction * instr = new Instruction( OPCODE_SUB,
                                              Operande(t.getName(idnTemp)),
                                              Operande($1),
                                              Operande(t.getName($3.id))
                                              );
        //ajout de l'instruction au code 3@ global
        CCode->ajouterInstruction(instr);
      // hyper important on dit que la valeur de retour (.res dans les TD) est le temporaire, c'est la fonction d'affectation qui s'occupera de tout mettre bien
      $$.id = idnTemp;
      $$.type = $1.type;
      $$.calcule = false;
      
    }else if(!$1.calcule && $3.calcule){
      int idnTemp = t.ajouterTemporaire();
      Instruction lastLigne$1 = CCode->getLastVarAff(t.getName($1.id));
      
        tableCourante->ajouterTemporaire(idnTemp, $1.type);
        Instruction * instr = new Instruction( OPCODE_SUB,
                                              Operande(t.getName(idnTemp)),
                                              Operande(t.getName($1.id)),
                                              Operande($3)
                                              );
        CCode->ajouterInstruction(instr);
      
      $$.id = idnTemp;
      $$.type = $3.type;
      $$.calcule = false;
    }else{
      int idnTemp = t.ajouterTemporaire();
      Instruction lastLigne$3 = CCode->getLastVarAff(t.getName($3.id));
      tableCourante->ajouterTemporaire(idnTemp, $3.type);
      Instruction * instr = new Instruction( OPCODE_SUB,
                                              Operande(t.getName(idnTemp)),
                                              Operande(t.getName($1.id)),
                                              Operande(t.getName($3.id))
                                              );
      CCode->ajouterInstruction(instr);
      $$.id = idnTemp;
      $$.type = $1.type;
      $$.calcule = false;
    }
}
// fin ajout du OP moin //

// debut ajout du OP MULT //

void ExprOP_MULExpr(c3a & $$, c3a & $1, c3a & $3){
    if($1.calcule && $3.calcule && ( ($1.type == Type::TYPE_INTEGER && $3.type == Type::TYPE_REAL) || ($3.type == Type::TYPE_INTEGER && $1.type == Type::TYPE_REAL) ) )
    {
        switch($1.type){
            case Type::TYPE_INTEGER :
                $1.type = Type::TYPE_REAL;
                $1.f = $1.i;
                break;
            case Type::TYPE_REAL :
                $3.type = Type::TYPE_REAL;
                $3.f = $3.i;
                break;							
        }
    }
  if($1.type != $3.type){
      yyerror((char*)"Multiplication de deux types incompatibles");
  }
    if($1.calcule && $3.calcule){
        if( $1.type == $3.type){
            switch($1.type){
                case Type::TYPE_INTEGER :
                    $$.i = $1.i * $3.i;
                    break;
                case Type::TYPE_REAL :
                    $$.f = $1.f * $3.f;
                    break;							
            }
            $$.type = $1.type;
            $$.calcule = true;
        }
    }else if($1.calcule && !$3.calcule){
    	
      int idnTemp = t.ajouterTemporaire();
      // recupération du de la derniere ligne dans le code 3@ avec pour operande gauche la variable qu'on cherche a ajouter
      Instruction lastLigne$3 = CCode->getLastVarAff(t.getName($3.id));
        //ajout du temporaire a la table des symboles 
        // TODO : on aura un probleme lors de l'appel de fonction
        tableCourante->ajouterTemporaire(idnTemp, $3.type);
        // Ligne clef on met dans le temporaire la somme des deux operande de l'addition.
        Instruction * instr = new Instruction( OPCODE_MUL,
                                              Operande(t.getName(idnTemp)),
                                              Operande($1),
                                              Operande(t.getName($3.id))
                                              );
        //ajout de l'instruction au code 3@ global
        CCode->ajouterInstruction(instr);
      // hyper important on dit que la valeur de retour (.res dans les TD) est le temporaire, c'est la fonction d'affectation qui s'occupera de tout mettre bien
      $$.id = idnTemp;
      $$.type = $1.type;
      $$.calcule = false;
      
    }else if(!$1.calcule && $3.calcule){
      int idnTemp = t.ajouterTemporaire();
      Instruction lastLigne$1 = CCode->getLastVarAff(t.getName($1.id));
      
        tableCourante->ajouterTemporaire(idnTemp, $1.type);
        Instruction * instr = new Instruction( OPCODE_MUL,
                                              Operande(t.getName(idnTemp)),
                                              Operande(t.getName($1.id)),
                                              Operande($3)
                                              );
        CCode->ajouterInstruction(instr);
      
      $$.id = idnTemp;
      $$.type = $3.type;
      $$.calcule = false;
    }else{
      int idnTemp = t.ajouterTemporaire();
      Instruction lastLigne$3 = CCode->getLastVarAff(t.getName($3.id));
      tableCourante->ajouterTemporaire(idnTemp, $3.type);
      Instruction * instr = new Instruction( OPCODE_MUL,
                                              Operande(t.getName(idnTemp)),
                                              Operande(t.getName($1.id)),
                                              Operande(t.getName($3.id))
                                              );
      CCode->ajouterInstruction(instr);
      $$.id = idnTemp;
      $$.type = $1.type;
      $$.calcule = false;
    }
}
// fin ajout du OP MULT //

// debut ajout du OP DIV //

void ExprOP_SLASHExpr(c3a & $$, c3a & $1, c3a & $3){
    if($1.calcule && $3.calcule && ( ($1.type == Type::TYPE_INTEGER && $3.type == Type::TYPE_REAL) || ($3.type == Type::TYPE_INTEGER && $1.type == Type::TYPE_REAL) ) )
    {
        switch($1.type){
            case Type::TYPE_INTEGER :
                $1.type = Type::TYPE_REAL;
                $1.f = $1.i;
                break;
            case Type::TYPE_REAL :
                $3.type = Type::TYPE_REAL;
                $3.f = $3.i;
                break;							
        }
    }
  if($1.type != $3.type){
      yyerror((char*)"Division de deux types incompatibles");
  }
    if($1.calcule && $3.calcule){
        if( $1.type == $3.type){
            switch($1.type){
                case Type::TYPE_INTEGER :
                    $$.f = float($1.i) / float($3.i);
                    printf("$$.f = %f $1.i = %d $3.i = %d",$$.f, $1.i,$3.i);

                    break;
                case Type::TYPE_REAL :
                    $$.f = $1.f / $3.f;
                    printf("$$.f = %f $1.f = %f $3.f = %f",$$.f, $1.f,$3.f);
                    break;							
            }
            $$.type = Type::TYPE_REAL;
            $$.calcule = true;
        }
    }else if($1.calcule && !$3.calcule){
    	
      int idnTemp = t.ajouterTemporaire();
      // recupération du de la derniere ligne dans le code 3@ avec pour operande gauche la variable qu'on cherche a ajouter
      Instruction lastLigne$3 = CCode->getLastVarAff(t.getName($3.id));
        //ajout du temporaire a la table des symboles 
        // TODO : on aura un probleme lors de l'appel de fonction
        tableCourante->ajouterTemporaire(idnTemp, $3.type);
        // Ligne clef on met dans le temporaire la somme des deux operande de l'addition.
        Instruction * instr = new Instruction( OPCODE_DIV,
                                              Operande(t.getName(idnTemp)),
                                              Operande($1),
                                              Operande(t.getName($3.id))
                                              );
        //ajout de l'instruction au code 3@ global
        CCode->ajouterInstruction(instr);
      // hyper important on dit que la valeur de retour (.res dans les TD) est le temporaire, c'est la fonction d'affectation qui s'occupera de tout mettre bien
      $$.id = idnTemp;
      $$.type = $1.type;
      $$.calcule = false;
      
    }else if(!$1.calcule && $3.calcule){
      int idnTemp = t.ajouterTemporaire();
      Instruction lastLigne$1 = CCode->getLastVarAff(t.getName($1.id));
      
        tableCourante->ajouterTemporaire(idnTemp, $1.type);
        Instruction * instr = new Instruction( OPCODE_DIV,
                                              Operande(t.getName(idnTemp)),
                                              Operande(t.getName($1.id)),
                                              Operande($3)
                                              );
        CCode->ajouterInstruction(instr);
      
      $$.id = idnTemp;
      $$.type = $3.type;
      $$.calcule = false;
    }else{
      int idnTemp = t.ajouterTemporaire();
      Instruction lastLigne$3 = CCode->getLastVarAff(t.getName($3.id));
      tableCourante->ajouterTemporaire(idnTemp, $3.type);
      Instruction * instr = new Instruction( OPCODE_DIV,
                                              Operande(t.getName(idnTemp)),
                                              Operande(t.getName($1.id)),
                                              Operande(t.getName($3.id))
                                              );
      CCode->ajouterInstruction(instr);
      $$.id = idnTemp;
      $$.type = $1.type;
      $$.calcule = false;
    }
}
// fin ajout du OP DIV //

void VarExprOP_AFFECTExpression(c3a & $$,c3a & $1,c3a & $3){
  if($1.type == $3.type){
    Instruction * instr;
    if($3.calcule){
      instr = new Instruction(OPCODE_CPY, Operande(t.getName($1.id)),Operande($3));
    }else{
      instr = new Instruction(OPCODE_CPY, Operande(t.getName($1.id)),Operande(t.getName($3.id)));      
    }
    CCode->ajouterInstruction(instr);
    $$.calcule = false;
    $$.type = $1.type;
    $$.idres = $1.id;
  	}else if($1.type == Type::TYPE_INTEGER && $3.type==Type::TYPE_REAL){
	    Instruction * instr;
	    if($3.calcule){
		  $3.i=$3.f;
		  $3.type=Type::TYPE_INTEGER;
	      instr = new Instruction(OPCODE_CPY, Operande(t.getName($1.id)),Operande($3));
	    }else{
	      instr = new Instruction(OPCODE_CPY, Operande(t.getName($1.id)),Operande(t.getName($3.id)));      
	    }
	    CCode->ajouterInstruction(instr);
	    $$.calcule = false;
	    $$.type = $1.type;
	    $$.idres = $1.id;
  	}else if($1.type == Type::TYPE_REAL && $3.type==Type::TYPE_INTEGER){
	    Instruction * instr;
	    if($3.calcule){
		  $3.f=$3.i;
		  $3.type=Type::TYPE_REAL;
	      instr = new Instruction(OPCODE_CPY, Operande(t.getName($1.id)),Operande($3));
	    }else{
	      instr = new Instruction(OPCODE_CPY, Operande(t.getName($1.id)),Operande(t.getName($3.id)));      
	    }
	    CCode->ajouterInstruction(instr);
	    $$.calcule = false;
	    $$.type = $1.type;
	    $$.idres = $1.id;
  }else{
    yyerror((char *)"l'affectation se fait entre deux types différents\n");
  }
}


void varExprParseIdent(c3a & $$,c3a & $1){
    Categorie * c = tableCourante->getCategorie($1.id);
    if(c->TYPE == Categorie::VARIABLE){
        Variable & v = *static_cast<Variable *>(c);
        switch(v.getType()){
            case Type::TYPE_INTEGER :
                $$.type = Type::TYPE_INTEGER;
                //printf("Variable entiere\n");
                break;
            case Type::TYPE_REAL :
                $$.type = Type::TYPE_REAL;
                //printf("Variable reelle\n");
                break;
            case Type::TYPE_STRING :
                $$.type = Type::TYPE_STRING;
                //printf("Variable String\n");
                break;
            case Type::TYPE_BOOL:
                $$.type = Type::TYPE_BOOL;
                //printf("Variable Bool\n");
                break;			
        }
    }else if(c->TYPE == Categorie::PROGRAM){
        //cout << "une programme\n" << endl;
    }else if(c->TYPE == Categorie::PROCEDURE){
        //cout << "une procedure\n" << endl;
    }else if(c->TYPE == Categorie::FONCTION){
        //cout << "une fonction\n" << endl;
    }else if(c->TYPE == Categorie::ARGUMENT){	
        //cout << "un argument\n" << endl;
    }
  $$.id = $1.id;
  //$$.code = NULL;
}
void KW_AND_Expr(c3a & $$,c3a & $1,c3a & $3){
    if($1.calcule && $3.calcule){
        // $1 et $3 calculables
        if($1.type == Type::TYPE_BOOL && $3.type == Type::TYPE_BOOL){
            if($1.b && $3.b) { 
                  $$.b = true;
            }else{
                $$.b = false;
            }
            $$.type == Type::TYPE_BOOL;
            $$.calcule = true;
        }else{
            yyerror( (char * )"$1 && $3 , $1.type ou $3.type != TYPE_BOOL \n");
        }
    }else if($1.calcule || $3.calcule){
      int idnTemp = t.ajouterTemporaire();
      tableCourante->ajouterTemporaire(idnTemp, $1.type);
      Instruction * instr;
      if(!$1.calcule){
        Instruction derLign = CCode->getLastVarAff(t.getName($1.id));
        instr = new Instruction(OPCODE_AND ,
                                              Operande(t.getName(idnTemp)),
                                              Operande(t.getName($1.id)),
                                              Operande($3.b)
                                              );
      }else if(!$3.calcule){
        Instruction derLign = CCode->getLastVarAff(t.getName($3.id));
        instr = new Instruction( OPCODE_AND ,
                                              Operande(t.getName(idnTemp)),
                                              Operande($1.b),
                                              Operande(t.getName($3.id))
                                              );
      }
      CCode->ajouterInstruction(instr);
      $$.id = idnTemp;
      $$.type = $3.type;
      $$.calcule = false;
    }else{
      int idnTemp = t.ajouterTemporaire();
      tableCourante->ajouterTemporaire(idnTemp, $3.type);
      Instruction * instr;
      Instruction derLign = CCode->getLastVarAff(t.getName($3.id));
      instr = new Instruction(OPCODE_AND ,
                                Operande(t.getName(idnTemp)),
                                Operande(t.getName($1.id)),
                                Operande(t.getName($3.id))
                                );
      
      CCode->ajouterInstruction(instr);
      $$.id = idnTemp;
      $$.type = $3.type;
      $$.calcule = false;
    }
}

void KW_OR_Expr(c3a & $$, c3a & $1,c3a & $3){
    if($1.calcule && $3.calcule){
        // $1 et $3 calculables
        if($1.type == Type::TYPE_BOOL && $3.type == Type::TYPE_BOOL){
            if($1.b || $3.b) { 
                $$.b = true;
            }else{
                $$.b = false;
            }
            $$.type == Type::TYPE_BOOL;
            $$.calcule = true;
        }else{
            yyerror( (char * )"$1 || $3 , $1.type ou $3.type != TYPE_BOOL \n");
        }
    }else if($1.calcule || $3.calcule){
        // $1 ou $3 calculable
    }else{
        // $1 et $3 non calculables
    }
}
void KW_XOR_Expr(c3a & $$,c3a & $1,c3a & $3){
    if($1.calcule && $3.calcule){
        // $1 et $3 calculables
        if($1.type == Type::TYPE_BOOL && $3.type == Type::TYPE_BOOL){
            if($1.b != $3.b) { 
                $$.b = true;
            }else{
                $$.b = false;
            }
            $$.type == Type::TYPE_BOOL;
            $$.calcule = true;
        }else{
            yyerror( (char * )"$1 || $3 , $1.type ou $3.type != TYPE_BOOL \n");
        }
    }else if($1.calcule || $3.calcule){
        // $1 ou $3 calculable
    }else{
        // $1 et $3 non calculables
    }
}

void KW_NOT_Expr(c3a & $$,c3a & $2){
    if($2.calcule){
        // $2 calculable
        if($2.type == Type::TYPE_BOOL){
            $$.b = !$2.b;
            $$.type == Type::TYPE_BOOL;
            $$.calcule = true;
        }else{
            yyerror( (char * )"$1.type  != TYPE_BOOL \n");
        }
        
    }else{
        // $2 non calculables
    }
}
#endif 
